package com.example.auth.service.impl;

import com.example.auth.model.dto.UserDTO;
import com.example.auth.model.vo.PayloadVO;
import com.example.auth.service.IAuthService;
import com.example.common.api.ResultUtil;
import com.example.common.util.GsonUtil;
import com.example.feign.user.UserFeignClient;
import com.example.redis.constants.Constant;
import com.example.redis.util.RedisUtil;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.jwk.RSAKey;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.security.KeyPair;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.*;
import java.util.concurrent.*;

@Service
@Slf4j
public class AuthService implements IAuthService {

    @Value("${jwt.subject}")
    private String subject;

    @Value("${jwt.name}")
    private String name;

    @Value("${jwt.password}")
    private String password;

    @Value("${jwt.expire}")
    private long expire;

    @Resource
    private UserFeignClient userFeignClient;

    @Resource
    private ThreadPoolTaskExecutor poolTaskExecutor;

    @Resource
    private RedisUtil redisUtil;

    /**
     * 获取默认的RSAKey
     */
    private RSAKey getDefaultRSAKey() {
        //从classpath下获取RSA秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(name), password.toCharArray());
        KeyPair keyPair = keyStoreKeyFactory.getKeyPair(name.substring(0, name.indexOf(".")), password.toCharArray());
        //获取RSA公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        //获取RSA私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        return new RSAKey.Builder(publicKey).privateKey(privateKey).build();
    }

    /**
     * 生成Payload
     */
    private PayloadVO getPayloadVO(String userId, List<String> authorities) {
        Date now = new Date();
        long exp = now.getTime() + expire;
        return PayloadVO.builder()
                .subject(subject)
                .iat(now.getTime())
                .exp(exp)
                .jti(UUID.randomUUID().toString())
                .userId(userId)
                .authorities(authorities)
                .build();
    }

    /**
     * 使用RSA算法生成token
     */
    private String generateTokenByRSA(String payloadStr, RSAKey rsaKey) throws JOSEException {
        //创建JWS头，设置签名算法和类型
        JWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.RS256)
                .type(JOSEObjectType.JWT)
                .build();
        // 将负载信息封装到Payload中
        Payload payload = new Payload(payloadStr);
        // 创建JWS对象
        JWSObject jwsObject = new JWSObject(jwsHeader, payload);
        // 创建RSA签名器
        JWSSigner jwsSigner = new RSASSASigner(rsaKey);
        // 签名
        jwsObject.sign(jwsSigner);
        return jwsObject.serialize();
    }

    @Override
    public ResultUtil createToken(Map<String, String> params) throws JOSEException, ExecutionException, InterruptedException {
        log.info("create token begin, receive params: {}", params);

        String mobile = params.getOrDefault("mobile", "");
        String password = params.getOrDefault("password", "");
        if (StringUtils.isBlank(mobile)){
            return ResultUtil.validateFailed("缺少字段mobile");
        }
        if (StringUtils.isBlank(password)){
            return ResultUtil.validateFailed("缺少字段password");
        }

        // 通过feign调用demo-user服务，获取用户信息

        // openfeign在版本3.1.1后请求需要在线程中进行
        // block()/blockFirst()/blockLast() are blocking, which is not supported in thread parallel-1
        ResultUtil resultUtil = poolTaskExecutor.submit(()->userFeignClient.getUser(mobile,password)).get();

        if (resultUtil.getCode() == 200){
            UserDTO userDTO = GsonUtil.toObject(GsonUtil.toMap(resultUtil.getData()), UserDTO.class);
            PayloadVO defaultPayload = getPayloadVO(userDTO.getId().toString(), userDTO.getRoleCode());
            String token = generateTokenByRSA(GsonUtil.toString(defaultPayload), getDefaultRSAKey());
            Map<String, String> map = new HashMap<>();
            map.put("token", token);

            //把token存储到redis中
            log.info("save token to redis start");
            redisUtil.set(Constant.USER_TOKEN_PREFIX + userDTO.getId(), token);
            redisUtil.set(Constant.USER_INFO_PREFIX + userDTO.getId(), GsonUtil.toString(userDTO));
            log.info("save token to redis end");

            return ResultUtil.success(map);
        }

        return resultUtil;
    }
}
